package com.leyou.trade.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.leyou.common.exception.LyException;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SkuDTO;
import com.leyou.trade.constants.PayConstants;
import com.leyou.trade.dto.OrderFormDTO;
import com.leyou.trade.entity.Order;
import com.leyou.trade.entity.OrderDetail;
import com.leyou.trade.entity.OrderLogistics;
import com.leyou.trade.entity.enums.OrderStatus;
import com.leyou.trade.mapper.OrderMapper;
import com.leyou.trade.service.OrderService;
import com.leyou.trade.utils.PayHelper;
import com.leyou.trade.utils.UserHolder;
import com.leyou.user.client.UserClient;
import com.leyou.user.dto.AddressDTO;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.Valid;
import java.util.*;

/**
 * @ClassName : OrderServiceImpl  //类名
 * @Description :   //描述
 * @Author : qingfeng  //作者
 * @Date: 2020-09-26 15:07  //时间
 */
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    private OrderDetailServiceImpl detailService;
    private OrderLogisticsServiceImpl logisticsService;
    private ItemClient itemClient;
    private UserClient userClient;
    private PayHelper payHelper;

    public OrderServiceImpl(OrderDetailServiceImpl detailService, OrderLogisticsServiceImpl logisticsService, ItemClient itemClient, UserClient userClient,PayHelper payHelper) {
        this.detailService = detailService;
        this.logisticsService = logisticsService;
        this.itemClient = itemClient;
        this.userClient = userClient;
        this.payHelper = payHelper;
    }

    @Override
    @Transactional
    public Long createOrder(@Valid OrderFormDTO orderDTO) {
        //1 写order
        Order order = new Order();
        //1.1 用户id
        Long userId = UserHolder.getUser();
        order.setUserId(userId);
        //1.2 金额相关信息
        Map<Long, Integer> carts = orderDTO.getCarts();
        //1.2.1 获取所有sku的id
        ArrayList<Long> idList = new ArrayList<>(carts.keySet());
        //1.2.2 查询sku
        List<SkuDTO> skuList = itemClient.querySkuByIds(idList);
        System.out.println("userId:"+userId+"carts:"+carts+"idList"+idList+"skuList:"+skuList);

        //1.2.3 计算金额的和
        long total = 0;
        for (SkuDTO sku: skuList) {
            //获取金额
            int num = carts.get(sku.getId());
            //计算总金额
            total += sku.getPrice() * num;
        }
        //1.2.4 填写金额数据
        order.setTotalFee(total);
        order.setPaymentType(orderDTO.getPaymentType());
        order.setPostFee(0L);//全场包邮
        order.setActualFee(total + order.getPostFee()/* 优惠金额 */ );
        //1.3 订单状态初始化
        order.setStatus(OrderStatus.INIT);
        //1.4 写order到数据库
        boolean success = save(order);
        if (!success) {
            throw new LyException(500,"订单创建失败");
        }
        //2. 写orderdetail
        //2.1 定义orderdetail的集合
        ArrayList<OrderDetail> details = new ArrayList<>();
        //2.2 遍历sku集合，转为detail
        for (SkuDTO skuDTO : skuList) {
            //2.2.1 商品数量
            int num = carts.get(skuDTO.getId());
            //2.2.2 组装orderdetail
            OrderDetail detail = new OrderDetail();
            detail.setOrderId(order.getOrderId());
            detail.setImage(StringUtils.substringBefore(skuDTO.getImages(),","));
            detail.setNum(num);
            detail.setSkuId(skuDTO.getId());
            detail.setSpec(skuDTO.getSpecialSpec());
            detail.setPrice(skuDTO.getPrice());
            detail.setTitle(skuDTO.getTitle());
            //2.2.3 装入detail集合
            details.add(detail);
        }
        //2.3 批量新增
        success = detailService.saveBatch(details);
        if (!success){
            throw new LyException(500,"订单创建失败");
        }
        //3 写orderlogistics
        //3.1 查询收货地址
        AddressDTO address = userClient.queryAddressById(userId);
        //3.2 填写物流信息
        OrderLogistics logistics = address.toEntity(OrderLogistics.class);
        logistics.setOrderId(order.getOrderId());
        //3.3 写入数据库
        success = logisticsService.save(logistics);
        if (!success) {
            throw new LyException(500,"订单创建失败！");
        }
        //4 减库存
        try {
            itemClient.deductStock(carts);
        } catch (FeignException e) {
            throw new LyException(e.status(),e.contentUTF8());
        }
        //返回订单编号
        return order.getOrderId();
    }

    @Override
    public String getPayUrl(Long orderId) {
        /**
         * @Description //TODO 根据订单编号创建支付链接
         * @Param [orderId]
         * @return java.lang.String
         */
        //根据id查询订单
        Order order = getById(orderId);
        //判断是否存在
        if (order == null) {
            throw new LyException(400,"订单编号错误，订单不存在！");
        }
        //判断订单状态是否是未付款
        if (order.getStatus()!= OrderStatus.INIT) {
            //订单已经关闭或者已经支付，无需再次获取支付链接
            throw new LyException(400,"订单已经支付或者关闭！");
        }
        //TODO 尝试读取redis中的支付url

        //获取订单金额
        Long actualFee = order.getActualFee();
        //统一下单
        String url = payHelper.unifiedOrder(orderId, actualFee);
        //TODO 把支付的url缓存在redis中，2小时有效期

        //返回url
        return url;
    }

    @Override
    @Transactional
    public void handleNotify(Map<String, String> data) throws Exception {
        /**
         * @Description //TODO 微信支付成功回调
         * @Param [data]
         * @return void
         */
        // 1.业务标示校验
        payHelper.checkResultCode(data);
        // 2.签名校验
        payHelper.checkSignature(data);

        // 3.订单状态校验（保证幂等，防止重复通知）
        String outTradeNo = data.get(PayConstants.ORDER_NO_KEY);
        String totalFee = data.get(PayConstants.TOTAL_FEE_KEY);
        if (StringUtils.isBlank(outTradeNo) || StringUtils.isBlank(totalFee)) {
            // 数据有误
            throw new RuntimeException("响应数据有误，订单金额或编号为空！");
        }
        Long orderId = Long.valueOf(outTradeNo);
        Order order = getById(orderId);
        if (!order.getStatus().equals(OrderStatus.INIT)) {
            // 说明订单已经支付过了，属于重复通知，直接返回
            return;
        }

        // 4.订单金额校验
        Long total = Long.valueOf(totalFee);
        if (!total.equals(order.getActualFee())) {
            throw new RuntimeException("订单金额有误，我要报警了！");
        }

        // 5.修改订单状态，更新状态和支付时间两个字段
        update().set("status", OrderStatus.PAY_UP.getValue()).set("pay_time", new Date())
                // 条件包括订单id和订单状态必须为1，乐观锁保证幂等
                .eq("order_id", orderId).eq("status", OrderStatus.INIT.getValue());
        log.info("处理微信支付通知成功！{}", data);
    }

    @Override
    public Integer queryPayStatus(Long orderId) {
        Order order = getById(orderId);
        if (order == null){
            throw new LyException(404,"订单不存在");
        }
        return order.getStatus().getValue();
    }

    @Override
    @Transactional
    public void evictOrderIfNecessary(Long orderId) {
        /**
         * @Description //TODO 清理订单
         * @Param [orderId]
         * @return void
         */
        //1.查询订单
        Order order = getById(orderId);
        if (order==null){
            //订单不存在，无需处理
            return;
        }
        //2.判断订单是否支付
        if (order.getStatus()!=OrderStatus.INIT){
            //订单已处理，无需重复处理
            return;
        }
        // 3.如果未支付，需要关闭订单
        boolean boo = update().set("status", OrderStatus.CLOSED.getValue())
                .set("close_time", new Date())
                .eq("order_id", orderId)
                // 通过乐观锁进一步保证幂等效果
                .eq("status", OrderStatus.INIT.getValue())
                // 执行update
                .update();
        if(!boo){
            // 更新失败，订单状态已经改变，无需处理
            return;
        }
        log.info("已关闭超时未支付订单：{}", orderId);
        // 4.查询OrderDetail
        List<OrderDetail> details = detailService.query().eq("order_id", orderId).list();

        // 5.获取商品及商品数量信息
        Map<Long, Integer> skuMap = new HashMap<>();
        // 得到其中的商品和数量信息
        for (OrderDetail detail : details) {
            skuMap.put(detail.getSkuId(), detail.getNum());
        }
    /*Map<Long, Integer> map = details.stream()
        .collect(Collectors.toMap(OrderDetail::getSkuId, OrderDetail::getNum));*/

        // 6.恢复库存
        itemClient.addStock(skuMap);
    }
}
